/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package eu.easyedu.dictionary.api.nservice.impl;

import eu.easyedu.dictionary.api.model.Dictionary;
import eu.easyedu.dictionary.api.model.Language;
import eu.easyedu.dictionary.api.model.Word;
import eu.easyedu.dictionary.api.model.WordRelationship;
import eu.easyedu.dictionary.api.util.Configuration;
import eu.easyedu.dictionary.api.util.ConnectionDoesNotExistsException;
import eu.easyedu.dictionary.api.util.DatabaseUtils;
import eu.easyedu.dictionary.api.util.EQLStatement;
import eu.easyedu.dictionary.api.util.Executable;
import eu.easyedu.persistence.DefaultPersistenceProvider;
import eu.easyedu.persistence.EntityInfo;
import eu.easyedu.persistence.Mapper;
import eu.easyedu.persistence.PersistenceProvider;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author hlavki
 */
public class PersistenceUtil {

    private static final Logger log = Logger.getLogger(PersistenceUtil.class.getName());

    public static <T> T executeInPersitenceContext(Dictionary dict, Executable<T> executable) {
        T result = null;
        try {
            Connection conn = DatabaseUtils.getConnection(dict).getJDBCConnection();
            result = executable.execute(conn, getPersistenceUnit());
        } catch (SQLException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
        } catch (ConnectionDoesNotExistsException e) {
            log.log(Level.SEVERE, e.getMessage(), e);
        } finally {
//            try {
//                if (conn != null) {
//                    ConnectionManager.getDefault().disconnect(conn);
//                }
//            } catch (SQLException e) {
//                log.log(Level.SEVERE, "FATAL ERROR: " + e.getMessage(), e);
//            }
        }
        return result;
    }
    private static PersistenceProvider pu = null;

    public static synchronized PersistenceProvider getPersistenceUnit() {
        if (pu == null) {
            pu = DefaultPersistenceProvider.getInstance();
            pu.registerEntity(Word.class);
            pu.registerEntity(Language.class);
            pu.registerEntity(WordRelationship.class);
        }
        return pu;
    }

    public static <T> T getEntityById(Dictionary dict, final T idObject, final Class<T> clazz) {
        if (idObject == null) {
            return null;
        }
        String objName = "obj";
        PersistenceProvider pp = getPersistenceUnit();
        final Mapper<T> mapper = pp.getEntityMapper(clazz);
        StringBuffer sb = new StringBuffer("SELECT ");
        mapper.getSelectColumns(sb, objName).append(" FROM ");
        sb.append(mapper.getEntityInfo().getTable()).append(" ").append(objName).append(" WHERE ");
        final EQLStatement sqlCond = buildQueryCriteria(null, idObject, mapper.getEntityInfo());
        sb.append(sqlCond.getHQL());
        final String sql = sb.toString();
        if (log.isLoggable(Level.FINE)) {
            log.fine(sql);
        }

        T result = executeInPersitenceContext(dict, new Executable<T>() {

            public T execute(Connection conn, PersistenceProvider pu) throws SQLException {
                PreparedStatement stmt = conn.prepareStatement(sql);
                sqlCond.prepareParams(stmt);
                ResultSet rs = stmt.executeQuery();
                rs.next();
                return mapper.map(rs);
            }
        });
        return result;
    }

    private static EQLStatement buildQueryCriteria(String alias, Object example, EntityInfo<?> entityInfo) {
        if (example == null) {
            return null;
        }
        EQLStatement stmt = null;
        try {
            Set<String> includeClasses = Configuration.QUERY_BY_EXAMPLE_INLCUDE_CLASSES;
            if (log.isLoggable(Level.FINE)) {
                log.fine("INCLUDE: " + includeClasses);
            }
            BeanInfo info = Introspector.getBeanInfo(example.getClass());
            PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
            for (int idx = 0; idx < descriptors.length; idx++) {
                PropertyDescriptor descriptor = descriptors[idx];
                if (descriptor.getPropertyType() == null ||
                        Collection.class.isAssignableFrom(descriptor.getPropertyType())) {
                    log.fine("collection handling not supported: skipping property: " + descriptor.getName());
                    continue;
                }
                if (descriptor.getReadMethod().getDeclaringClass().equals(Object.class)) {
                    log.fine("property defined on Object class: skipping: " + descriptor.getName());
                    continue;
                }
                Object value = descriptor.getReadMethod().invoke(example, (Object[]) null);
                if (log.isLoggable(Level.FINE)) {
                    log.fine("value of " + descriptor.getName() + " = " + value);
                }
                if (value == null) {
                    log.fine("property value is null: skipping: " + descriptor.getName());
                    continue;
                }
                Class<?> returnType = descriptor.getReadMethod().getReturnType();
                if (!returnType.isPrimitive() && !includeClasses.contains(returnType.getName())) {
                    log.fine("property is unknown type or not primitive [" + returnType.getName() + "]");
                    // no recursion
                    // buildQueryCriteria(alias + "." + descriptor.getName(), value, stmt);
                    continue;
                }
                if (stmt == null) {
                    stmt = new EQLStatement();
                } else {
                    stmt.append(" AND ");
                }
                if (alias != null) {
                    stmt.append(alias).append(".");
                }
                stmt.append(entityInfo.getColumnInfo(descriptor.getName()).getName()).append("=?");
                stmt.addParameter(value);
                if (log.isLoggable(Level.FINE)) {
                    log.fine("add criteria: " + alias + " = " + value);
                }
                //                sb.append(descriptor.getName());
            }
        } catch (Exception e) {
            log.log(Level.SEVERE, "hql example criterion building error", e);
            return null;
        }
        return stmt;
    }
}
